// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

//! PVF host integration tests checking the chain production pipeline.

use super::TestHost;
use codec::{Decode, Encode};
use polkadot_node_primitives::PoV;
use polkadot_parachain_primitives::primitives::{
	BlockData as GenericBlockData, HeadData as GenericHeadData,
};
use polkadot_primitives::PersistedValidationData;
use sp_core::H256;
use test_parachain_adder::{hash_state, BlockData, HeadData};

#[tokio::test]
async fn execute_good_block_on_parent() {
	let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) };
	let block_data = BlockData { state: 0, add: 512 };
	let pvd = PersistedValidationData {
		parent_head: GenericHeadData(parent_head.encode()),
		relay_parent_number: 1u32,
		relay_parent_storage_root: H256::default(),
		max_pov_size: 4096 * 1024,
	};
	let pov = PoV { block_data: GenericBlockData(block_data.encode()) };

	let host = TestHost::new().await;

	let ret = host
		.validate_candidate(
			test_parachain_adder::wasm_binary_unwrap(),
			pvd,
			pov,
			Default::default(),
			H256::default(),
		)
		.await
		.unwrap();

	let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();

	assert_eq!(new_head.number, 1);
	assert_eq!(new_head.parent_hash, parent_head.hash());
	assert_eq!(new_head.post_state, hash_state(512));
}

#[tokio::test]
async fn execute_good_chain_on_parent() {
	let mut parent_hash = [0; 32];
	let mut last_state = 0;

	let host = TestHost::new().await;

	for (number, add) in (0..10).enumerate() {
		let parent_head =
			HeadData { number: number as u64, parent_hash, post_state: hash_state(last_state) };
		let block_data = BlockData { state: last_state, add };
		let pvd = PersistedValidationData {
			parent_head: GenericHeadData(parent_head.encode()),
			relay_parent_number: 1u32,
			relay_parent_storage_root: H256::default(),
			max_pov_size: 4096 * 1024,
		};
		let pov = PoV { block_data: GenericBlockData(block_data.encode()) };

		let ret = host
			.validate_candidate(
				test_parachain_adder::wasm_binary_unwrap(),
				pvd,
				pov,
				Default::default(),
				H256::default(),
			)
			.await
			.unwrap();

		let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();

		assert_eq!(new_head.number, number as u64 + 1);
		assert_eq!(new_head.parent_hash, parent_head.hash());
		assert_eq!(new_head.post_state, hash_state(last_state + add));

		parent_hash = new_head.hash();
		last_state += add;
	}
}

#[tokio::test]
async fn execute_bad_block_on_parent() {
	let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) };
	let block_data = BlockData {
		state: 256, // start state is wrong.
		add: 256,
	};
	let pvd = PersistedValidationData {
		parent_head: GenericHeadData(parent_head.encode()),
		relay_parent_number: 1u32,
		relay_parent_storage_root: H256::default(),
		max_pov_size: 4096 * 1024,
	};
	let pov = PoV { block_data: GenericBlockData(block_data.encode()) };

	let host = TestHost::new().await;

	let _err = host
		.validate_candidate(
			test_parachain_adder::wasm_binary_unwrap(),
			pvd,
			pov,
			Default::default(),
			H256::default(),
		)
		.await
		.unwrap_err();
}

#[tokio::test]
async fn stress_spawn() {
	let host = std::sync::Arc::new(TestHost::new().await);

	async fn execute(host: std::sync::Arc<TestHost>) {
		let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) };
		let block_data = BlockData { state: 0, add: 512 };
		let pvd = PersistedValidationData {
			parent_head: GenericHeadData(parent_head.encode()),
			relay_parent_number: 1u32,
			relay_parent_storage_root: H256::default(),
			max_pov_size: 4096 * 1024,
		};
		let pov = PoV { block_data: GenericBlockData(block_data.encode()) };
		let ret = host
			.validate_candidate(
				test_parachain_adder::wasm_binary_unwrap(),
				pvd,
				pov,
				Default::default(),
				H256::default(),
			)
			.await
			.unwrap();

		let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();

		assert_eq!(new_head.number, 1);
		assert_eq!(new_head.parent_hash, parent_head.hash());
		assert_eq!(new_head.post_state, hash_state(512));
	}

	futures::future::join_all((0..100).map(|_| execute(host.clone()))).await;
}

// With one worker, run multiple execution jobs serially. They should not conflict.
#[tokio::test]
async fn execute_can_run_serially() {
	let host = std::sync::Arc::new(
		TestHost::new_with_config(|cfg| {
			cfg.execute_workers_max_num = 1;
		})
		.await,
	);

	async fn execute(host: std::sync::Arc<TestHost>) {
		let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) };
		let block_data = BlockData { state: 0, add: 512 };
		let pvd = PersistedValidationData {
			parent_head: GenericHeadData(parent_head.encode()),
			relay_parent_number: 1u32,
			relay_parent_storage_root: H256::default(),
			max_pov_size: 4096 * 1024,
		};
		let pov = PoV { block_data: GenericBlockData(block_data.encode()) };
		let ret = host
			.validate_candidate(
				test_parachain_adder::wasm_binary_unwrap(),
				pvd,
				pov,
				Default::default(),
				H256::default(),
			)
			.await
			.unwrap();

		let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();

		assert_eq!(new_head.number, 1);
		assert_eq!(new_head.parent_hash, parent_head.hash());
		assert_eq!(new_head.post_state, hash_state(512));
	}

	futures::future::join_all((0..5).map(|_| execute(host.clone()))).await;
}
